/*
 * XInput1_3-dev - Replacement of xinput library to help with creating
 *                 drivers/debugging application
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */

#include "stdafx.h"

#include "DeviceInfo.h"
#include "XInputCore.h"
#include "DriverComm.h"
#include "DeviceEnum.h"
#include "DeviceList.h"
#include "Utilities.h"

CRITICAL_SECTION g_csGlobalLock;
bool g_IsInitialized = false;
bool g_IsCommunicationEnabled = false;

bool XInputCore::Close(void)
{
	g_IsInitialized = false;

	XInputCore::EnableCommunications(0);

	Utilities::Close();
	DeviceList::Close();
	DeviceEnum::Close();

	DriverComm::Initialize();

	DeleteCriticalSection(&g_csGlobalLock);

	return true;
}

bool XInputCore::EnableCommunications(bool bEnabled)
{
	bool bResult = g_IsCommunicationEnabled;
	if ( g_IsCommunicationEnabled != bEnabled )
	{
		g_IsCommunicationEnabled = bEnabled;
		bResult = DeviceInfo::OnEnableSettingChanged(bEnabled);
		for (DWORD i = 0; i < XUSER_MAX_COUNT; ++i )
		{
			PDEVICE_INFO pDeviceInfo = 0;
			if ( SUCCEEDED(DeviceEnum::GetDeviceOnPort(i, &pDeviceInfo, 0)) )
			{
				if ( pDeviceInfo )
				{
					XINPUT_VIBRATION Vibration = {0};
					PXINPUT_VIBRATION pVibration = &Vibration;

					return SUCCEEDED(DeviceInfo::g_pfnSetVibrationDispatcher(pDeviceInfo, (LPVOID*)&pVibration, 1));
				}
			}
		}
	}
	return bResult;
}

HRESULT XInputCore::Enter(void)
{
	if ( g_IsInitialized )
	{
		EnterCriticalSection(&g_csGlobalLock);
		return S_OK;
	}

	return E_FAIL;
}

bool XInputCore::Initialize(void)
{
	InitializeCriticalSection(&g_csGlobalLock);

	if ( SUCCEEDED(Utilities::Initialize()) )
	{
		if ( SUCCEEDED(DeviceList::Initialize()) )
		{
			if ( SUCCEEDED(DeviceEnum::Initialize()) )
			{
				if ( SUCCEEDED(DriverComm::Initialize()) )
				{
					XInputCore::EnableCommunications(true);
					g_IsInitialized = true;
					return true;
				}

				DeviceEnum::Close();
			}

			DeviceList::Close();
		}

		Utilities::Close();
	}

	return false;
}

HRESULT XInputCore::Leave(void)
{
	if ( g_IsInitialized )
	{
		LeaveCriticalSection(&g_csGlobalLock);
		return S_OK;
	}
	
	return E_FAIL;
}

HRESULT XInputCore::ProcessAPIRequest(ulong dwPort, APIFunction_t APICall, PVOID lpArgs[], uchar ArgC)
{
	bool bRetry = false;

	HRESULT hResult = XInputCore::Enter();
	if ( FAILED(hResult) )
	{
		return hResult;
	}

	PDEVICE_INFO pDeviceInfo = 0;

	while( true )
	{
		hResult = DeviceEnum::GetDeviceOnPort(dwPort, &pDeviceInfo, g_IsCommunicationEnabled);
		if ( FAILED(hResult) )
		{
			break;
		}

		if ( pDeviceInfo )
		{
			hResult = APICall(pDeviceInfo, lpArgs, ArgC);

			if ( FAILED(hResult) )
			{
				pDeviceInfo->dwFlags &= ~1;
			}

			if ( hResult != S_FALSE )
			{
				if ( pDeviceInfo->dwFlags & 1 )
				{
					break;
				}
				else
				{
					pDeviceInfo = 0;
					hResult = DeviceList::RemoveDeviceFromPort(dwPort);
					if ( FAILED(hResult) )
						break;

					if ( bRetry )
					{
						//bRetry = false;
						hResult = S_FALSE;
						break;
					}
					bRetry = true;
				}
			}
		}
		else
		{
			//bRetry = false;
			hResult = S_FALSE;
			break;
		}
	}

	XInputCore::Leave();

	return hResult;
}